Reloading Table View After Watching a Rewarded Video - swift

Recently I've implemented the rewarded video feature in the code and it works well. Simply, when the user chooses the level from the table view and watches the rewarded video, it unlocks the level. But when the user closes the ad view, table view stays the same, not reloaded with the new information.
I tried some solution I found here but nothing worked.

You probably need to reload the table view manually using tableView.reloadData(). Either do this in viewWillAppear or in any notifications/callbacks that you trigger when the ad has finished playing.

You can set event notification on the reward video opening controller. Use these lines of code in viewDidLoad
GADRewardBasedVideoAd.sharedInstance().delegate = self
GADRewardBasedVideoAdDelegate notifies you of rewarded video lifecycle events. You are required to set the delegate prior to loading an ad. The most important event in this delegate is rewardBasedVideoAd:didRewardUserWithReward:, which is called when the user should be rewarded for watching a video. You may optionally implement other methods in this delegate.
Then you need to implement this delegate
func rewardBasedVideoAd(_ rewardBasedVideoAd: GADRewardBasedVideoAd,
didRewardUserWith reward: GADAdReward) {
print("Reward received with currency: \(reward.type), amount \(reward.amount).")
// RELOAD YOUR TABLE VIEW DATA HERE
}
For details you can see detail implementation here

you can simply do it using NSNotification to call your tableView.reloadData():
add
NotificationCenter.default.addObserver(self, selector: #selector(reload), name: NSNotification.Name(rawValue: "load"), object: nil)
to ur tableView Controller's (TVC) ViewDidLoad
and this function to ur TVC
#objc func reload()
{
//setup ur new data
self.tableView.reloadData()
}
then just add
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
befor ur self.dismiss(animated: true, completion: nil) in ur RewardedVideo Player

Related

How to determine whether current view controller is active, and execute code if active

In my app, there is a ViewController.swift file and a popupViewController.swift file. Inside the app, when I open the popupViewController with storyboard segue as presentModally and then come back from popupViewController to ViewController with the code dismiss(), the methods viewDidLoad, viewWillAppear, viewDidAppear, ViewWillLayoutSubviews etc. nothing works, they execute just once and don't repeat when I go and return back. So, I want to execute the code every time when viewController.swift is active. I couldn't find a useful info in stackoverflow about this.
Meanwhile, I don't know much about notification and observers(if certainly needed), therefore, can you tell step by step in detail how to do that in Swift (not objective-c)? I mean how to determine if current view controller is active.
Edit: I am navigating from StoryBoard segue, presentModally. There is no Navigation Controller in storyboard.
I tried some codes but nothing happens. The point I came so far is:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector:#selector(appWillEnterForeground), name:UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appWillEnterForeground() {
print("asdad") //nothing happens
if self.viewIfLoaded?.window != nil {
// viewController is visible
print("CURRENT VİEW CONTROLLER") //nothing happens
}
}
As mention in my comments, I don't use storyboards. There may be a way to create an unwind segue - or maybe not - but [here's a link][1] that may help you with a storyboard-only way of fixing your issue. A quick search on "modal" turned up 9 hits, and the second one starts going into details.
I'm thinking the issue is with what modality is. Basically, your first view controller, which properly executed viewDidAppear, is still visible. So it's effectively not executing viewDidDisappear when your second VC is presented.
You might want to change your concept a bit - an application window (think AppDelegate and/or SceneDelegate become active, where a UIViewController has a is initialized and deinitialized, along with a root UIView that is loaded, appears* and disappears*. This is important, because what you want to do is send your notification from the modal VC's viewDidDisappear override.
First, I find it easiest to put all your notication definitions in an extension:
extension Notification.Name {
static let modalHasDisappeared = Notification.Name("ModalHasDisappeared")
}
This helps not only reduce string typos but also is allows Xcode's code completion to kick in.
Next, in your first view controller, ad an observer to this notification:
init() {
super.init(nibName: nil, bundle: nil)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared), name: .modalHasDisappeared, object: nil)
}
#objc func modalHasDisappeared() {
print("modal has disappeared")
}
I've added both forms of init for clarity. Since you are using a storyboard, I'd expect that init(coder:) is the one you need.
Finally, just send the notification when the modal has disappeared:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: .modalHasDisappeared, object: nil, userInfo: nil)
}
This sends no data, just the fact that the modal has disappeared. If you want to send data - say, a string or a table cell value, change the object parameter to it:
NotificationCenter.default.post(name: .modalHasDisappeared, object: myLabel, userInfo: nil)
And make the following changes in your first VC:
NotificationCenter.default.addObserver(self, selector: #selector(modalHasDisappeared(_:)), name: .modalHasDisappeared, object: nil)
#objc func modalHasDisappeared(_ notification:Notification) {
let label = notification.object as! UILabel!
print(label.text)
}
Last notes:
To repeat, note that by declaring an extension to Notification.Name, I've only have one place where I'm declaring a string.
There is no code in AppDelegate or SceneDelegate, nor any references to `UIApplication(). Try to think of the view (and view controller) as appearing/disappearing, not background/foreground.
While the first view is visually in the background, it's still visible. So the trick is to code against the modal view disappearing instead.

NSNotification observer not being removed unless it has been triggered once

In summary
If a user takes a photo with an overlay on VC1 it triggers a notification. The corresponding observer gets removed by my method as a I present VC2.
If the user doesn't take a photo on VC1, the notification observer isn't trigger. The same removal method is called as VC2 is presented but the observer stays active and causes unwanted behaviours with VC2 photo capture.
Full explanation
I have a registration form where a user can take an optional photo of their face. I'm using a simple UIImagePickerController and presenting a 'passport' style overlay.
if sourceType == .camera {
imagePickerController.cameraDevice = .front
let overlay = PassportOverlayView(frame: imagePickerController.view.frame)
imagePickerController.cameraOverlayView = overlay
}
The overlay covers the 'retake' and 'choose' buttons of the UIImagePickerController so I observe the following NSNotifications to remove the overlay when a photo taken, and re-add it if required should the user wish to retake their photo.
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = nil
})
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object:nil, queue:nil, using: { note in
self.imagePickerController.cameraOverlayView = PassportOverlayView(frame: self.imagePickerController.view.frame)
})
Everything works as intended. I remove the notifications before the next UIViewController is pushed onto the stack.
func removeObservers(){
print("remove Observers")
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object: nil)
}
If the user has taken a photo and therefore called the observer adding a photo on the next UIViewController, which doesn't require an overlay, works fine.
If however, the user doesn't take a photo on the first UIViewController [which is perfectly fine for the purposes of my app] they run into an issue on the second UIViewController.
In both scenerios the removeObservers() function is called. "remove Observers" is printed to the console on both occasions. Yet when the users tries to take a photo from within the second UIViewController the app crashes as it Unexpectedly found nil while implicitly unwrapping an Optional value and points me to the self.imagePickerController.cameraOverlayView = nil line of the _UIImagePickerControllerUserDidCaptureItem notification.
I understand the error, it's trying to remove a cameraOverlayView that isn't there. What I can't understand is why the observer is still there when I believe it's already been removed. If the user takes the first photo with the cameraOverlayView and triggers the _UIImagePickerControllerUserDidCaptureItem notification there isn't an issue. The observer is removed and subsequent UIImagePickerControllers don't have the same issue.
Any help would be greatly appreciated.
I think I know what your problem is. A retain cycle is causing the observer to not be removed successfully. The reason why you have a retain cycle is because you are using a strong reference to self in the closure where you set your notifications. Here is how you fix it:
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object:nil, queue:nil, using: { [unowned self] note in
self.imagePickerController.cameraOverlayView = nil
})
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object:nil, queue:nil, using: { [unowned self] note in
self.imagePickerController.cameraOverlayView = PassportOverlayView(frame: self.imagePickerController.view.frame)
})
Again, you shouldn't have to worry about removing the observers, so try to run your code without removeObserver and it should work.
Hope this helps.

Swift Notification Not Firing

I am having a little bit of trouble. I am trying to use the notification center to alert the app that some content is done loading. After the user signs up or logs in successfully I create the view controller and then and then make it the root view controller using the blow
func finishLoggingIn() {
// print("Finish logging in from LoginController")
let homeController = HomeViewController()
self.loginButton.stopAnimation(animationStyle: .expand, completion: {
self.view.window?.rootViewController = homeController
self.view.window?.makeKeyAndVisible()
})
}
The login button just creates a loading animation on the button for UI purposes.
When I first enter the controller I add the observer for the notification.
let MainVCSetup = Notification.Name("mainVCComplete")
NotificationCenter.default.addObserver(self, selector: #selector(handleRootViewSwitch), name: MainVCSetup, object: nil)
When the content in my mainVC is done loading I post this same notification to the Notification Center like so
NotificationCenter.default.post(name: MainVCSetup, object: nil)
However this function never fires off no matter what I do
#objc func handleRootViewSwitch(){
print("Trying to handle root view switch attack")
NotificationCenter.default.removeObserver(self, name: MainVCSetup, object: nil)
}
If anyone notices where I went wrong I would greatly appreciate it.

Creating an observer to check if MediaPlayer playbackState is paused or not

I have a music app and I wish to determine if playback has been paused while the app was closed (due to an event like a phone call or AirPods being taken out of ear etc)
My first approach was to run a func inside of viewWillAppear that checked
if mediaPlayer.playbackState == .paused {
...
}
If it was paused I updated the play/pause button image. However, this did not work, the play/pause button would still show Play even if it was paused.
Next, I tried adding an observer to the viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(self.wasSongInterupted(_:)), name: UIApplication.didBecomeActiveNotification, object: self.mediaPlayer)
The self.wasSongInterupted I call is
#objc func wasSongInterupted(_ notification: Notification) {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .paused {
print("paused")
self.isPlaying = false
self.playPauseSongButton.isSelected = self.isPlaying
} else if self.mediaPlayer.playbackState == .playing {
self.isPlaying = true
self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
However, I am still having the same issue.
What is the best way to determine if my music player is playing or paused when I reopen the app?
Edit 1: I Edited my code based on comments.
wasSongInterrupted was not being called, and through breakpoints and errors I discovered the code was mostly not needed. I changed my code to be
func wasSongInterrupted() {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .interrupted {
var isPlaying: Bool { return self.mediaPlayer.playbackState == .playing }
print("Playback state is \(self.mediaPlayer.playbackState.rawValue), self.isPlaying Bool is \(self.isPlaying)")
self.playPauseSongButton.setImage(UIImage(named: "playIconLight"), for: .normal)
//self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
and inside my AppDelegate's applicationDidBecomeActive I have
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
Now the code runs, however I have an issue.
If I run the following code:
if self.mediaPlayer.playbackState == .interrupted {
print("interrupted \(self.isPlaying)")
}
and then make a call and come back to the app it will hit the breakpoint. It will print out interrupted as well as false which is the Bool value for self.isPlaying
However if I try to update the UI by
self.playPauseSongButton.isSelected = self.isPlaying
or by
self.playPauseSongButton.setImage(UIImage(named: "playIconLight.png"), for: .normal)
I get an error message Thread 1: EXC_BREAKPOINT (code=1, subcode=0x104af9258)
You trying to update you player UI from viewWillAppear. From Apple Documentation:
viewWillAppear(_:)
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view.
So if your app was suspended and the becomes active again, this method won't be called, because your UIViewController is already at Navigations Stack.
If you want to catch the moment when your app becomes active from suspended state, you need to use AppDelegate. From Apple Documentation:
applicationDidBecomeActive(_:)
This method is called to let your app know that it moved from the inactive to active state. This can occur because your app was launched by the user or the system.
So you need to use this method at your AppDelegate to handle app running and update your interface.
UPDATE
You saying the inside this AppDelegate method you're doing
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
That's wrong because you're creating a new view controller. What you need to do, is to access you existing view controller from navigation stack and update it.
One of the possible solutions is to use NotificationCenter to send a notification. You view controller should be subscribed to this event of course.
At first, you need to create a notification name
extension Notification.Name {
static let appBecameActive = Notification.Name(rawValue: "appBecameActive")
}
Then in you AppDelegate add following code to post your notifications when app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
NotificationCenter.default.post(name: .appBecameActive, object: nil)
}
And finally in your view controller add to subscribe it on notifications
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(wakeUp),
name: .appBecameActive,
object: nil)
...
}
#objc func wakeUp() {
// Update your UI from here
}
Hope it helps you.

NSNotification addObserver in two ViewControllers Swift

In my HomeViewController viewDidLoad method I have an observer that looks out for a new notification. When observed it segues to a SecondTableVC. I have an observer in the second VC looking for the same notification, but the second observer isn't seeing the notification and calling the method. Thanks in advance to anyone who can explain what I'm missing here? I removed the observer in both viewDidLoad and in the segue method, but it doesn't fix it.
var childVC: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
childVC = self.storyboard?.instantiateViewControllerWithIdentifier("WordListsTableViewController")
// check for new notification - if there is segue to the SecondTableVC
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(HomeViewController.showChildVC), name: "NotificationActionPressed", object: nil) // Segue works fine.
}
func showChildVC() {
self.view.addSubview(childVC.view)
}
In SecondTableVC
override func viewDidLoad() {
super.viewDidLoad()
// check for new notification - if there is
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SecondTableVC.newNotif), name: "NotificationActionPressed", object: nil)
}
func newNotif() {
print("new notif") // THIS METHOD IS NOT GETTING CALLED
}
Kind of piggybacking off of the answer from Phillip: If it is absolutely necessary that the second view controller listen to the NSNotification event, then the second view controller can be instantiated from the storyboard and held in memory by the first view controller until it needs to be displayed. In this case, the second view controller should subscribe to the notification event upon initialization.
Your segue causes the second view controller to be created. If the segue is triggered by a notification, then the SecondTableVC's viewDidLoad hasn't happened when the notification fires.
The second controller's not receiving the notification because it not only hasn't registered by that time, but doesn't actually exist.