1

I made my very first iOS app. But there are two annoying bugs which I cannot get rid of. I hope somebody can help me!

The app is supposed to train to read musical notation. The user specifies his instrument and level (on the previous viewcontroller) and based on that, it places random notes in musical notation on the screen. The user should match those notes in textfields and the app keeps track of the score and advances a level after ten right answers.

However, somehow I'm having problems with the function which generates the random notes. The function for some reason gets called twice, the first time it generates the notes, saves them in a global variable and creates the labels with the notes. The second time, it changes the global variable but not the labels. It returns the following error message this time: 2018-09-29 23:08:37.279170+0200 MyProject[57733:4748212] Warning: Attempt to present <MyProject.ThirdViewController: 0x7fc709125890> on <MyProject.SecondViewController: 0x7fc70900fcd0> whose view is not in the window hierarchy! Because of this, the user answers the question on the screen, but the app thinks it's the wrong answer, because it has the second answer stored.

The second time the user answers a question, the function is only called once, but the read-out from the text fields doesn't update to the new values, but keeps the same as with the first question.

Here is the code which gives the problems:

import UIKit

class ThirdViewController: UIViewController
{

// snip

func setupLabels() {
    // snip
    // here the random notes are created, this is function is called multiple times for some reason
    let antwoord = Noten()
    let antwoordReturn = antwoord.generateNoten(instrument: instrument, ijkpunt: ijkpunt, aantalNoten: aantalNoten-1)
    let sleutel = antwoordReturn.0
    let heleOpgave = antwoordReturn.1
    print(heleOpgave)
    print(PassOpgave.shared.category)
    let heleOpgaveNummers = antwoordReturn.2
    // snip
    var a = 0
    while a < aantalNoten {
        // the labels are created, no problems there
        let myTekstveld = UITextField(frame: CGRect(x: labelX, y: labelY + 150, width: labelWidth, height: labelHeight / 2))
        myTekstveld.backgroundColor = UIColor.white
        myTekstveld.textAlignment = .center
        myTekstveld.placeholder = "?"
        myTekstveld.keyboardType = UIKeyboardType.default
        myTekstveld.borderStyle = UITextField.BorderStyle.line
        myTekstveld.autocorrectionType = .no
        myTekstveld.returnKeyType = UIReturnKeyType.done
        myTekstveld.textColor = UIColor.init(displayP3Red: CGFloat(96.0/255.0), green: CGFloat(35.0/255.0), blue: CGFloat(123.0/255.0), alpha: 1)
        myTekstveld.delegate = self as? UITextFieldDelegate
        myTekstveld.tag = a + 1
        view.addSubview(myTekstveld)

        a += 1
        labelX += labelWidth
    }

    // the button is created
}

override func viewDidLoad()
{
    super.viewDidLoad()
    // snip
    setupLabels()
}

@objc func buttonAction(sender: UIButton!) {
    // snip
    // here the text from the text fields is read, but this only works the first time the buttonAction is called, the next times, it simply returns the first user input.
    while a <= aantalNoten {
        if let theLabel = view.viewWithTag(a) as? UITextField {
            let tekstInput = theLabel.text!
            userInput.append(tekstInput)
        }
        a += 1
    }

    // snip 

    setupLabels()
    return
}

// snip
Emile
  • 41
  • 1
  • 9
  • 9
    If you place a breakpoint in the function and run it in a debugger you’ll see exactly where it’s being called. That should help narrow it down – Sami Kuhmonen Sep 29 '18 at 07:25
  • Please include only _relevant_ code in your question. It's disrespectful to dump whole classes and make people read through code that has nothing to do with what you're asking. Please read [mcve] and [edit] your question. If necessary, create a new project containing your code, and then remove all unnecessary code - this may help you find the issue yourself. – Ashley Mills Sep 29 '18 at 08:36
  • 1
    @Ashley: I'm sorry if I was disrespectful. I had no idea where the mistake came from, some didn't know what was relevant and what not... I will edit the code. – Emile Sep 29 '18 at 18:26
  • @Sami I tried the breakpoint. It seams everything works fine and all the labels are placed and filled and then the function is called again and new values are created. Immediately after I get an error message I don't understand `2018-09-29 23:08:37.279170+0200 MyProject[57733:4748212] Warning: Attempt to present on whose view is not in the window hierarchy!` Does that make any sense to you? Does it want to change something in the previous view controller? I didn't ask it to do that, I think. – Emile Sep 29 '18 at 21:12

3 Answers3

2

You have two instances of ThirdViewController when you don't mean to.

This error is very telling:

2018-09-29 23:08:37.279170+0200 MyProject[57733:4748212] Warning: Attempt to present <MyProject.ThirdViewController: 0x7fc709125890> on <MyProject.SecondViewController: 0x7fc70900fcd0> whose view is not in the window hierarchy!

This is telling you that SecondViewController is trying to create ThirdViewController when SecondViewController is not even on the screen. This suggests that the mistake is in SecondViewController (perhaps observing notifications or other behaviors when not on screen). It's possible of course that you also have two instances of SecondViewController.

I suspect you're trying to build all of this by hand rather than letting Storyboards do the work for you. That's fine, but these kinds of mistakes are a bit more common in that case. The best way to debug this further is to set some breakpoints and carefully check the address of the objects (0x7fc709125890 for example). You'll need to hunt down where you're creating an extra one.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    Thank you! This solved the problem. The SecondViewController was actually very simple, it just asks two values from the user. I copied a segue code from the internet which was supposed to send these codes over to the ThirdViewController but didn't fully understand them. I deleted all of that code, created a class to have the variables always available and voila, solved! Thanks for pointing me to the SecondViewController!!! – Emile Sep 30 '18 at 00:17
1

Your genreteNoten method is being called multiple times because it is called from setupLabels which is In turn called from viewDidLoad.

viewDidLoad may be called multiple times and your code should account for that. As it says in this answer to a similar question:

If you have code that only needs to run once for your controller use -awakeFromNib.

Ali Beadle
  • 4,486
  • 3
  • 30
  • 55
  • 2
    "viewDidLoad may be called multiple times (and two at startup is typical)" – not typical at all in recent iOS versions. The answer you linked to is over 7 years old, I don't think it's relevant anymore. – Tim Vermeulen Sep 29 '18 at 18:48
  • @Ali how should I use -awakeFromNib exactly? I tried some things and googled a lot, but I still don't know. – Emile Sep 29 '18 at 20:52
  • @tim agreed, typical is misleading and I will remove that. I think the principle that is can be called multiple times remains valid though. – Ali Beadle Sep 30 '18 at 15:29
0

I managed to partially solve my second problem myself (that the read-out from the text fields was not updating to the second answer) by not creating them again. I added some code to setupLabels function to only create the text fields if there was no input already:

let myTekstveld = UITextField()
if (view.viewWithTag(a+1) as? UITextField) != nil {  
}
else {
myTekstveld.frame = CGRect(x: labelX, y: labelY + 100, width: labelWidth, height: labelHeight / 2)
// snip
myTekstveld.tag = a + 1
view.addSubview(myTekstveld)
}

The app works as expected now, the only problem is that the text fields are not cleared after each question.

Emile
  • 41
  • 1
  • 9