how to stop a dispatchQueue in swift - swift

I have a DispatchQueue for a introViewController that shows a gif for 11 seconds and then display my login page... But also have a button that skip the intro and display the login. When I click it the time still running and when I am navigating in the app goes back to the login when the time ends.
this is my class
class GifClass: UIViewController {
#IBOutlet weak var gifImage: UIImageView!
#IBOutlet weak var skipButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
gifImage.loadGif(name: "promed")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(11)) {
self.performSegue(withIdentifier: "introLogin", sender: self)
}
}
#IBAction func skip(_ sender: Any) {
performSegue(withIdentifier: "introLogin", sender: self)
}
}
how do I do to stop the time when I click the button?

For this, you can use a DispatchWorkItem. Here is the reference:
https://developer.apple.com/documentation/dispatch/dispatchworkitem
And here is an example of how you could use it in your code:
class GifClass: UIViewController {
#IBOutlet weak var gifImage: UIImageView!
#IBOutlet weak var skipButton: UIButton!
private var workItem: DispatchWorkItem? // Create a private DispatchWorkItem property
override func viewDidLoad() {
super.viewDidLoad()
gifImage.loadGif(name: "promed")
workItem = DispatchWorkItem { // Set the work item with the block you want to execute
self.performSegue(withIdentifier: "introLogin", sender: self)
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(11), execute: workItem!)
}
#IBAction func skip(_ sender: Any) {
workItem?.cancel() // Cancel the work item in the queue so it doesn't run
performSegue(withIdentifier: "introLogin", sender: self)
}
}

I'm not sure if there are best practices here, but I would consider doing what you are doing with a Timer rather than the DispatchQueue.
class GifClass: UIViewController {
#IBOutlet weak var gifImage: UIImageView!
#IBOutlet weak var skipButton: UIButton!
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
gifImage.loadGif(name: "promed")
timer = Timer.scheduledTimer(timeInterval: 11, target: self, selector: #selector(timerAction), userInfo: nil, repeats: false)
}
#objc func timerAction() {
performSegue(withIdentifier: "introLogin", sender: self)
}
#IBAction func skip(_ sender: Any) {
timer.invalidate()
performSegue(withIdentifier: "introLogin", sender: self)
}
}

You don't stop the queue. Instead, when the task that you dispatched starts executing, it needs to check its context and do the right thing (often: nothing) if the context has changed.

Related

Error : "Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee0234ff0)"

I have this code and when I am running the App on the line with "super.viewDidLoad()", I have the "Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee0234ff0" error...
Can someone help me please?
On internet they say me that I have to connect my Developer Account, but I don't have one.
This is the code :
import Foundation
import UIKit
import FirebaseAuth
import FirebaseDatabase
class SignUpController : UIViewController {
//Fonction pour scroller
private let scrollView: UIScrollView = {
let scrollView = UIScrollView.self()
scrollView.clipsToBounds = true
return scrollView
}()
//MARK : Outlets
#IBOutlet weak var artistnameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var confirmationPasswordTextField: UITextField!
#IBOutlet weak var signupButton: UIButton!
#IBOutlet weak var loginButton: UIButton!
//MARK : Properties
override func viewDidLoad() {
super.viewDidLoad()
viewDidLoad()
//Add subviews
view.addSubview(scrollView)
scrollView.addSubview(artistnameTextField)
scrollView.addSubview(emailTextField)
scrollView.addSubview(passwordTextField)
scrollView.addSubview(confirmationPasswordTextField)
scrollView.addSubview(signupButton)
scrollView.addSubview(loginButton)
//Design
setupButtons()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showLogin" {
let VCDestination = segue.destination as! LoginController
VCDestination.myMail = emailTextField.text!
}
if segue.identifier == "showLogin" {
let VCDestination = segue.destination as! LoginController
VCDestination.myPassword = passwordTextField.text!
}
}
//MARK : Private Methods
private func setupButtons() {
signupButton.layer.cornerRadius = 20
loginButton.layer.cornerRadius = 20
loginButton.layer.borderWidth = 3
loginButton.layer.borderColor = UIColor.lightGray.cgColor
}
private func setupTextFieldManager() {
artistnameTextField.delegate = self
emailTextField.delegate = self
passwordTextField.delegate = self
let tapGesture = UITapGestureRecognizer (target: self, action: #selector(hideKeyboard))
view.addGestureRecognizer(tapGesture)
}
//MARK : Actions
#IBAction func signUpButton(_ sender: UIButton) {
performSegue(withIdentifier: "showLogin", sender: nil)
}
#objc private func hideKeyboard() {
artistnameTextField.resignFirstResponder()
emailTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
}
#IBAction func signupButtonWasPressed(_ sender: UIButton) {
if artistnameTextField !== "" as AnyObject && emailTextField !== "" as AnyObject && passwordTextField !== "" as AnyObject && confirmationPasswordTextField.text == passwordTextField.text {
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) { (authResult, error) in
if error != nil {
print(error.debugDescription)
self.alertUserLoginError()
} else {
print ("Inscription en cours...")
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).setValue(["artistName": self.artistnameTextField.text])
self.performSegue(withIdentifier: "goToHome", sender: self)
}
}
} else {
print("Veuillez remplir tous les champs.")
}
}
#IBAction func loginButtonWasPressed(_ sender: UIButton) {
print("Redirection vers l'écran de connexion...")
}
func alertUserLoginError() {
let alert = UIAlertController(title: "Erreur", message: "Veuillez remplir tous les champs", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil))
present(alert, animated: true)
}
}
extension SignUpController: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Thanks
Ok so for the sake of brevity, here's a concept called "App Lifecycle" which by its nature is the methods that are called by the iOS system and in a specific order. Remember that computers can only run one thing at a time, per CPU. So it must go in an order of some sort. The iOS lifecycle events are as follows.
loadView()
viewDidLoad()
viewWillAppear()
viewDidAppear()
didReceiveMemoryWarning()
viewWillDisappear()
viewDidDisappear()
Now bear in mind there are a few others depending on what you're doing but these are the most common ones.
With 90% of these lifecycle methods, there's something more that is going on in the background that you don't see. This is where the super.methodName() call comes into play. It allows you to add additional functionality at a given lifecycle event without losing any of the other functionality that is being provided by that method. Otherwise, you might lose something that is required to load the view. Eg, super.methodName retains all previous functionality.
Now, to your issue, you have the following lines of code.
override func viewDidLoad() {
super.viewDidLoad()
viewDidLoad()
//A bunch more code after this.
}
Looking at the Lifecycle methods, that are required in many cases, notice that you are calling viewDidLoad() inside of your super.viewDidLoad which means that your main thread gets stuck. It loops back through that method that called it, and then there it is again, another viewDidLoad() call. In turn, it loops, again, and again, and again, until it crashes. There is a time and a place for recursion, however, this is not one of them, and that's for a completely separate topic.
Ultimately your solution is to remove the viewDidLoad() method call after your super.viewDidLoad() and that will resolve the error that you're having.

Adding Previous and Next buttons to Audio Player

I'm curious how to add functionality to the next/previous buttons. I have more .mp3 files in my project, but I'm not sure how to set the button functions to click through them. I think I need to set a variable and call the viewdidload() function inside the button function. I am just not 100% certain how to do that.
Noob here, be nice. :)
var player = AVAudioPlayer()
var timer = Timer()
#objc func updateScrubber(){
scrubber.value = Float(player.currentTime)
}
#IBAction func play(_ sender: Any) {
player.play()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.updateScrubber), userInfo: nil, repeats: true)
}
#IBAction func pause(_ sender: Any) {
player.pause()
}
#IBAction func previous(_ sender: Any) {
}
#IBAction func next(_ sender: Any) {
}
#IBAction func sliderMoved(_ sender: Any) {
player.volume = volume.value
}
#IBOutlet weak var volume: UISlider!
#IBAction func scrubberMoved(_ sender: Any) {
player.currentTime = TimeInterval(scrubber.value)
}
#IBOutlet weak var scrubber: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
let audioPath = Bundle.main.path(forResource: "threatofjoy", ofType: "mp3")
do {
try player = AVAudioPlayer(contentsOf: URL(fileURLWithPath: audioPath! ))
scrubber.maximumValue = Float(player.duration)
} catch {
print("Error")
}
I solved this by placing the songs in an array and creating a current song function to call in the button methods.

How can I properly send values between classes in Swift?

I've been working on a text based adventure game using Swift. However, I can't seem to change the default values for specific classes.
Below is the code for the class that allows me to select my player class
import UIKit
class ClassSelectionController: UIViewController
{
//Default class values
var character = (0, 0, "", 0)
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
}
//class button actions
#IBAction func fighterBtn(_ sender: Any)
{
character = (50, 60, "Steal Sword", 18)
performSegue(withIdentifier: "Character", sender: self)
}
#IBAction func wizerdBtn(_ sender: Any)
{
character = (25, 70, "Staff", 15)
performSegue(withIdentifier: "Character", sender: self)
}
#IBAction func thiefBtn(_ sender: Any)
{
character = (30, 60, "Dagger", 18)
performSegue(withIdentifier: "Character", sender: self)
}
#IBAction func archerBtn(_ sender: Any)
{
character = (50, 60, "Bow & Arrow", 16)
performSegue(withIdentifier: "Character", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: (Any)?)
{
//code for segue
var vc = segue.destination as! ViewController
vc.finalCharacter = self.character
}
}
And this is the class that receives the data for the player class and displays it.
import UIKit
class ViewController: UIViewController
{
var finalCharacter = (0, 0, "", 0)
//****************************************
//Setup for outlets go between these lines
//****************************************
#IBOutlet weak var healthLabel: UILabel!
#IBOutlet weak var damageLabel: UILabel!
#IBOutlet weak var weaponLabel: UILabel!
#IBOutlet weak var armorLabel: UILabel!
#IBOutlet weak var storyLabel: UILabel!
#IBOutlet weak var actionButton: UIButton!
#IBOutlet weak var northBtn: UIButton!
#IBOutlet weak var southBtn: UIButton!
#IBOutlet weak var eastBtn: UIButton!
#IBOutlet weak var westBtn: UIButton!
//****************************************
//Setup for outlets go between these lines
//****************************************
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
healthLabel.text = "Health: \(finalCharacter.0)"
damageLabel.text = "Damage: \(finalCharacter.1)"
weaponLabel.text = "Weapon: " + finalCharacter.2
armorLabel.text = "Armor : \(finalCharacter.3)"
}
//****************************************
//Setup for buttons go between these lines
//****************************************
#IBAction func actionButton(_ sender: Any)
{
}
#IBAction func northButton(_ sender: Any)
{
}
#IBAction func southButton(_ sender: Any)
{
}
#IBAction func eastButton(_ sender: Any)
{
}
#IBAction func westButton(_ sender: Any)
{
}
//****************************************
//Setup for buttons go between these lines
//****************************************
}
Even though my code does not currently show it I did put print values in the class buttons just so see if the values have changed when I select a class and I have seen that they do change when the button is pressed. I also poked around in ViewController and changed the finalCharacter values, just to see if that affected anything, which it didn't. So my educated guess is that the problem has to be in ClassSelectionController.
So the main problem is that I will click on the wizard player class, and I'd expect the class stats for the wizard to pop up (I.e. 25, 70, "Staff", 15), but I'll only get the default values that are in the ClassSelectionController (0, 0, "", 0).
What is happening here that you have multiple UIStoryboardSegue named "Character" linked with every button
so what happens is when you press the button the Segue is called before the Action button and in addition the UIStoryboardSegue is called again (if you place a debugger in the viewDidLoad you would see that it goes there two times).
Solution
Remove all the UIStoryboardSegue linked from the buttons
Make a new UIStoryboardSegue from the ClassSelectionController to the next ViewController name it 'Character'
You dont need to change anything from the code but should add a safe check
override func prepare(for segue: UIStoryboardSegue, sender: (Any)?)
{
if (segue.identifier == "Character") {
let vc = segue.destination as! ViewController
vc.finalCharacter = self.character
}
}

How to update UITableView when a CustomCell label value changes?

I have a custom cell class with two buttons and one label defined in its own class. Am using protocol-delegates to update the value of the label when the button is pressed. But am not able to figure out how to update the UITableView.
protocol customCellDelegate: class {
func didTapDecrementBtn(_ sender: AllCountersTableViewCell)
func didTapIncrementBtn(_ sender: AllCountersTableViewCell)
func updateTableViewCell(_ sender: AllCountersTableViewCell)
}
class AllCountersTableViewCell: UITableViewCell {
#IBOutlet weak var counterValueLbl: UILabel!
#IBOutlet weak var counterNameLbl: UILabel!
#IBOutlet weak var counterResetDateLbl: UILabel!
#IBOutlet weak var counterDecrementBtn: UIButton!
#IBOutlet weak var counterIncrementBtn: UIButton!
weak var delegate: customCellDelegate?
#IBAction func decrementBtnPressed(_ sender: UIButton) {
delegate?.didTapDecrementBtn(self)
delegate?.updateTableViewCell(self)
}
#IBAction func incrementBtnPressed(_ sender: UIButton) {
delegate?.didTapIncrementBtn(self)
delegate?.updateTableViewCell(self)
}
In my ViewController, I have provided the delegate. But reloadData() is not working, so although sender.counterLbl.value is changing its not reflecting on the view.
extension AllCountersVC: customCellDelegate {
func didTapIncrementBtn(_ sender: AllCountersTableViewCell) {
guard let tappedCellIndexPath = tableView.indexPath(for: sender) else {return}
let rowNoOfCustomCell = tappedCellIndexPath[1]
let newValue = String(allCounter[rowNoOfCustomCell].incrementCounterValue(by: allCounter[rowNoOfCustomCell].counterIncrementValue))
sender.counterValueLbl.text = newValue
}
func updateTableViewCell(_ sender: AllCountersTableViewCell) {
allCountersTableView.reloadData()
}
In cellForRow you must set cell.delegate = self for this logic to start working. Its not enough just to make you controller confroms to your custom cell delegate protocol. From your post I assume delegate is always nil in the cell, that is why it does not work.

Text field is permanently in the second view controller upon segue

I have been working on an app that allows multiple text fields from the first view controller pass over to the second view controller upon pressing a button. However, the text fields are permanently in the second view controller when I only want them to be if the button is pressed. Here is the code for the first view controller! Any help is greatly appreciated.
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textField1: UITextField!
#IBAction func buttonTwo(_ sender: Any) {
if textField1.text != "" {
performSegue(withIdentifier: "segue", sender: self)
}
}
#IBAction func buttonOne(_ sender: Any) {
if textField.text != "" {
performSegue(withIdentifier: "segue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var secondController = segue.destination as! SecondViewController
secondController.myString1 = textField1.text!
secondController.myString = textField.text!
}
}
Here is the code in the second view controller:
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var label1: UILabel!
var myString = String()
var myString1 = String()
override func viewDidLoad() {
super.viewDidLoad()
label.text = myString
label1.text = myString1
// Do any additional setup after loading the view.
}
}
Image of storyboard
This happens because, prepare for segue will be called every time you perform some segue action.
You should manage to have a bool variable that helps you track, if any button is clicked or not, if the segue is performed from the click of the button, then only you will have to set the text while preparing for segue.
here is your updated viewController
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var textField1: UITextField!
var isButtonClicked: Bool = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
/*reset isButtonClicked to false, when you back from second viewController */
isButtonClicked = false
}
#IBAction func buttonTwo(_ sender: Any) {
if textField1.text != "" {
isButtonClicked = true
performSegue(withIdentifier: "segue", sender: self)
}
}
#IBAction func buttonOne(_ sender: Any) {
if textField.text != "" {
isButtonClicked = true
performSegue(withIdentifier: "segue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if isButtonClicked {
var secondController = segue.destination as! SecondViewController
secondController.myString1 = textField1.text!
secondController.myString = textField.text!
}
}
}
Try and share your results.