How to automatically play audio when entering a screen - swift

I am learning Swift, and I am throwing myself in the deep end by building an app to force myself to learn the language. Something I want to implement is that when I transfer from screen 1 to screen 2, audio is played without needing to do anything. The audio is A-14a. The code below is set up so that when I click the Directions button, it plays the audio, but I don't know how to do it right away. Images are below to help show what I mean.
1st Screen 2nd Screen
The code I have is below:
import UIKit
import AVFoundation
class Intervention_Numerals1: UIViewController {
#IBOutlet weak var Directions: UIButton!
#IBOutlet weak var Done: UIButton!
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
setUpElements()
//Audio Test
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "A-N14a", ofType:"mp3")!))
audioPlayer.prepareToPlay()
} catch {
print(error)
}
}
func setUpElements() {
// Style the elements
Utilities.styleFilledButton(Directions)
Utilities.styleFilledButton(Done)
}
#IBAction func Play(_ sender: Any) {
audioPlayer.play()
}
}
Please let me know any advice on how to do this

Use this method
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
audioPlayer.play()
}

There are a couple of methods you can override to know the state of the view controller and add code to run when that state occurs.
As mentioned by Amila what you are looking for is probably viewDidAppear,
Another method that might also help you achieve this is viewWillAppear
I will provide the code of most of these methods and what they do below so you can try them all and experiment with them:
override func viewDidLoad() {
//This is what you already use every time, its called when the VC's view is loaded into memory
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//This happens when the view is *about* to appear, it happens before users see anything from the view, great for updating UI
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//This happens when the view actually appears on screen and when all the animations of loading the View Controller are over.
//not so good for updating UI since users will see a glimpse of previous view data.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//This happens when the view is *about* to disappear, great example for this is when you begin swiping to go back in a navigation controller.
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
//This happens when view is no longer on screen for user to see, like when that swiping back animation in using navigation controller is *finished*
}
There are a few more of these such as viewWillLayoutSubviews and viewDidLayoutSubviews that occur when views inside your View Controller change.
Use all these to control when what happens, hope this helps :)

Related

viewDidDisappear not showing ad

Im using startApp to display ads but when the view disappears it doesn't show the ad. I have startAppAd = STAStartAppAd() in viewDidLoad() I'm not quite sure what is going wrong.
override func viewDidAppear(_ animated: Bool) {
startAppAd?.load()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
startAppAd?.show()
}
viewDidDisappear is called when the vc is about to dimiss or popped , so any property is in it's way to deallocate if that vc is not strongly linked , so move the show method also inside viewDidAppear or in the ad load finish callback if any

self.tabBarController.selectedIndex not calling viewDidAppear

I've been looking at and trying all the solutions others have posted to this problem, but I'm still not getting it.
My use case is very simple. I have a viewController with a button, when that button is pressed I want to navigate to another tab and reload the data including an api call.
When using the button, I navigate to the tab fine, but viewDidAppear is not being called.
If on another tab, and navigate using the tab bar, viewDidAppear works fine. Also viewWillAppear is working, but I have to add a manual delay to the functions I want to call so it's not ideal.
So what do I need to do to navigate using self.tabBarController.selectedIndex = 0 and get the functionality of viewDidAppear?
Update: The viewWillAppear method I added gets called but I have to add a delay to my functions in order for them to work, and it's a bit clunky, not ideal. Not sure why viewDidAppear will not work :(
Here is a screenshot of the structure:
I appreciate any help on this one!
The "current" ViewController is my tab index 2:
import UIKit
class PostPreviewVC: UIViewController {
//Here I create a post object and post it to the timeline with the below button
#IBAction func postButtonPressed(_ sender: Any) {
//create the post via Firebase api
self.tabBarController?.selectedIndex = 0
}
}
In my destination viewController:
import UIKit
import Firebase
import SDWebImage
import AVFoundation
class HomeVC: UIViewController {
// MARK: - PROPERTIES
var posts = [Post]()
let refreshControl = UIRefreshControl()
//more properties...
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
_ = self.view
setupUI()
configureTableView()
reloadTimeline()
UserFirebase.timeline { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewWillAppear")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.reloadTimeline()
self.configureTableView()
}
}
//All the tableview code below here...
}
Added a custom class for my tab bar controller:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear in tabBar custom Class called")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear in tabBar custom Class called")
}
}
When you are using UITabBarController, the method viewDidLoad will called only once when any UIViewController is loaded in memory. After that, when you are navigating the same UIViewController, then it will load from memory.
In order to overcome this problem, you must divide your code in viewDidLoad & viewDidAppear. So, in viewDidLoad, you only put that code which you want to intialize once throughout the app such as adding UIView's or other things, while in viewDidAppear / viewWillAppear, you can make API calls or some functions which fetches dynamic data.
Finally, when you are calling self.tabBarController.selectedIndex = 0, it will call viewDidLoad only once and viewDidAppear / viewWillAppear every time when you are navigating that UIViewController.
Hope this helps to understand like how UITabBarController works.
For UITabBarController viewDidLoad only gets called once. and your viewWillAppear and viewDidAppear get called multiple times. you can either check if your viewWillAppear gets called or not. because your view will appear gets called before your viewDidAppear it's just like going through the reverse engineering process.
You can also add viewDidAppear method into your UITabBarController custom class. and call its superclass method into it in that way I think it will solve your problem.
Note: In the case of UITabbarController, Always do your UI update task and API calling a task in either
viewWillAppear or viewDidAppear

Hiding Bottom Bar When Pushed - Animation freezing on Iphone X

I've encountered an issue that I cannot figure out for the life of me. The issue is only happening on the iPhone X. I've added a little video since its difficult to explain exactly whats happening.
I've also added a screenshot of my storyboard so you can see the flow.
Pretty much were experiencing a freeze when the tab bar is being hidden. It only happens when we visit the category VC (which is presented modally using a segue, it is also embedded in a navigation controller.)
** I'm still new to iOS development so if I'm doing anything terribly wrong feel free to share :)
Video:
https://youtu.be/HC14zFxh-HM
Code that sends to reader:
#IBAction func sendToReader(_ sender: Any) {
let myVC = storyboard?.instantiateViewController(withIdentifier: "ReaderRootVC") as! ReaderRootVC
myVC.book = self.book
myVC.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(myVC, animated: true)
}
Code that closes Category VC:
#IBAction func navigationCancelBtnPressed(_ sender: Any) {
self.navigationController?.dismiss(animated: false, completion: nil)
}
Storyboard:
In your ReaderRootVC,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Hide the Tab Bar
self.tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Show the Tab Bar
self.tabBarController?.tabBar.isHidden = false
}

How to Stop a VCs functions from operating when VC is not in view? [Swift 3.0 Xcode]

I have a view controller which contains functions wish I need to disable once I leave the view controller. The functions wont start until I navigate to the VC, which is what I want, but I also what these functions to stop once I leave and navigate to other view controllers. Does anyone know any tricks to this?
there are multiple ways you can do this.
One like others have commented is invalidate timers or location stuff in either one of these methods.
let someTimer = Timer()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
someTimer.invalidate()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
someTimer.invalidate()
}

Need to free memory after Modal Segues, but I need both my Segues to pass data from A to B and B to A

I am making a game for iOS with SpriteKit.
I have 2 Viewcontrollers. One is the GameViewController and the other one is the MenuViewController. Let's call them A and B respectively.
When the player dies, a function is called in GameScene.swift that launches a modal "Lost" Segue to B. There, the player can restart the game or buy a life and a "Back" Segue is called to A.
I need to dismiss the additional Views that get created each time I call a segue.
Problem is: I need the "Lost" Segue to send data about the Score to View B and I need the "Back" Segue to send data to View A about wether or not the player used a life.
I have implemented all this. But now I need to find how to dismiss old views that keep eating the device's memory, thus leading to lag and crash.
I have googled for hours and hours. No solution was adapted to my situation.
The solutions I found either caused my app to bug, data not to be passed or views not to be generated.
I will not add code here since there is a LOT. But I am sure the answer is actually really easy, just not for a beginner like me.
I think a possible solution would be an unwind segue from B to A ?
But do unwind segues pass data along ?
Moreover, I found no answer I could understand on how to use an unwind segue.
I exhausted all my possibilities. Stack Exchange is my last chance.
You definitely should use an unwind segue to return to the previous viewController, otherwise as you have found your memory usage increases until your apps quits.
I created the following example from your description. It uses a standard segue to move from the GameViewController to the MenuViewController and it uses an unwind segue to move from the MenuViewController back to the GameViewController.
The GameViewController has a Player Dies UIButton, a UITextField for entering a score, and a UILabel for displaying the lives.
The MenuViewController has a UILabel for showing the score, a Buy a Life UIButton for adding lives, and a Restart UIButton for returning to the GameViewController.
Here's the code:
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
updateLivesLabel()
}
// This is the function that the unwind segue returns to.
// You can call it anything you want, but it has to be in
// the viewController you are returning to, it must be tagged
// with #IBAction and it must take a UIStoryboardSegue as its
// only parameter.
#IBAction func returnFromMenu(segue: UIStoryboardSegue) {
print("We're back in GameViewController")
// Update the lives label based upon the value passed in
// prepareForSegue from the MenuViewController.
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
self.performSegueWithIdentifier("Lost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Lost" {
let destinationVC = segue.destinationViewController as! MenuViewController
destinationVC.score = Int(scoreTextField.text ?? "") ?? 0
destinationVC.lives = lives
}
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
self.performSegueWithIdentifier("Back", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Back" {
let destinationVC = segue.destinationViewController as! GameViewController
destinationVC.lives = lives
}
}
}
This is how you wire up the forward segue to be called programmatically:
Control-drag from ViewController icon to the MenuViewController:
Select Present Modally from the pop-up:
Click on the segue arrow between the viewControllers and give it an identifier in the Attributes Inspector:
This is how you wire up the unwind segue to be called programmatically:
Control-drag from ViewController icon to Exit icon:
Choose returnFromMenu from pop-up:
Click on the Unwind Segue in the Document Outline and give it the identifier "Back" in the Attributes Inspector on the right:
Alternate Answer
Instead of using segues, you can present and dismiss viewControllers manually. The advantage for your app is that the MenuViewController will be allocated only once and will persist for the life of the app. This same viewController will be presented and dismissed repeatedly, but it will not be deallocated which I suspect is leading to your crashes.
The GameViewController will be the initialViewController that is created by the Storyboard. The MenuViewController will be loaded in viewDidLoad of the GameViewController.
To make this work, you need to add an identifier to the MenuViewController so that it can be instantiated by name. Click on the MenuViewController in the Storyboard and set its Storyboard ID in the Identity Inspector:
Here is the code. Note that all mention of segues is gone. Note how viewWillAppear is used to update the viewControllers.
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var menuViewController: MenuViewController?
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
menuViewController = self.storyboard!.instantiateViewControllerWithIdentifier("MenuViewController") as? MenuViewController
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
menuViewController?.score = Int(scoreTextField.text ?? "") ?? 0
menuViewController?.lives = lives
self.presentViewController(menuViewController!, animated: true, completion: nil)
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
let destinationVC = self.presentingViewController as! GameViewController
destinationVC.lives = lives
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}