Xcode находит переменную nil, где ни один не должен быть при вызове методов из другого класса ViewController - PullRequest
0 голосов
/ 19 октября 2019

На полпути я создал приложение для MacOS (мое первое приложение, которое не входит в учебное пособие «Ваше первое приложение»), включающее множество пользовательских опций в главном окне, и ему надоел тот факт, что мой ViewControllerфайл превратился в громоздкий беспорядок, который не собирался обслуживать в долгосрочной перспективе.

Я решил разбить его, вводя несколько контроллеров представлений в более мелкие порции, чтобы сделать его более управляемым, используя представления контейнеров в UIBuilder для встраивания представлений,но все учебники, которые я нашел, были либо для устаревших версий Xcode / Swift, либо для управления несколькими представлениями в iOS, поэтому мне пришлось немного экстраполировать, и я, возможно, сделал это неправильно.

Теперь я 'я получаю сообщение об ошибке метода в одном ViewController, когда метод вызывается другим ViewController, даже если этот метод работает, находят при вызове его собственным контроллером представления.

Либо я пропускаю что-то очевидное, либо я установилвсе не так.

Глобальные переменные:

var inputPathUrl = URL?
var outputExtension: String = ""

@ IBOutletsи локальные свойства для класса InOutViewController:

@IBOutlet weak var inputTextDisplay: NSTextField!
@IBOutlet weak var outputTextDisplay: NSTextField!
@IBOutlet weak var inputBrowseButton: NSButton!
@IBOutlet weak var outputBrowseButton: NSButton!

var outputDirectoryUrl: URL?
var inputFilePath: String = ""

@ IBOutlets для класса OptionsViewController

@IBOutlet weak var Button1: NSButton!
@IBOutlet weak var Button2: NSButton!
@IBOutlet weak var Button3: NSButton!
@IBOutlet weak var Button4: NSButton!
@IBOutlet weak var Button5: NSButton!

Методы для класса InOutViewController:

@IBAction func InputBrowseClicked(\_ sender: Any) {  
    let inputPanel = NSOpenPanel()  
    inputPanel.canChooseFiles = true  
    inputPanel.canChooseDirectories = false  
    inputPanel.allowsMultipleSelection = false  
    inputPanel.allowedFileTypes = \["aax"\]  
    let userChoice = inputPanel.runModal()  
    switch userChoice{  
        case .OK : 
        if let inputFileChosen = inputPanel.url {
            inputFileUrl = inputFileChosen // define global variable that will be called by other methods in other classes to check if an input file has been chosen
            updateInputText() // call methods to display path strings in text fields
            updateOutputText()
        }
        case .cancel :
            print("user cancelled")
        default :
            break
        }
    } 

@IBAction func outputBrowseClicked(_ sender: Any) {
    let outputPanel = NSOpenPanel()
    outputPanel.canChooseFiles = false
    outputPanel.canChooseDirectories = true
    outputPanel.allowsMultipleSelection = false
    let userChoice = outputPanel.runModal()
    switch userChoice{
        case .OK :
            if let outputUrl = outputPanel.url {
                outputDirectoryUrl = outputUrl
                updateOutputText()
        }
        case .cancel :
            print("user cancelled")
        default:
            break
    }
}

func updateInputText() {
    // call getOutputOption method to see which radio button is selected 
    OptionsViewController().getOutputOption() 
        if inputFileUrl != nil {
            inputFilePath = inputFileUrl!.path
            inputTextDisplay.stringValue = inputFilePath
         }
    }
func updateOutputText() {
    // derive output file path and name from input if no output location is chosen 
    if inputFileUrl != nil && outputDirectoryUrl == nil {
        let outputDirectory = inputFileUrl!.deletingPathExtension()
        let outputDirectoryPath = outputDirectory.path
        let outputPath = outputDirectoryPath + "(outputExtension)"
        outputTextDisplay.stringValue = outputPath
    } else if inputFileUrl != nil && outputDirectoryUrl != nil {
     // derive default file name from input but use selected output path if one is chosen
        let outputDirectoryPath = outputDirectoryUrl!.path
        let outputFile = inputFileUrl!.deletingPathExtension()
        let outputFilename = outputFile.lastPathComponent
        // derive file extension from getOutputOption method of OptionsViewController class
        let outputPath = outputDirectoryPath + "/" + outputFilename + "(outputExtension)"
        outputTextDisplay.stringValue = outputPath
       }
    }

Эта последняя строка (outputTextDisplay.stringValue = outputPath) - это то, что я получаю фатальную ошибку, но ТОЛЬКО когда я вызываю этот метод из @IBAction для переключателей формата вывода в OptionsViewController, чтобы обновить отображение вывода, когдавыбрано другое расширение файла. Когда я вызываю метод из методов действий в InOutViewController, он работает нормально.

Вот методы @IBAction и getOutputOption из класса OptionsViewController:

@IBAction func radioButtonClicked(_ sender: Any) {
    getOutputOption()
        // update display with new file extension
    InOutViewController().updateOutputText()
}

func getOutputOption() {
    // make sure an input file has been chosen
        if inputFileUrl != nil {
                // check which radio button is selected and derive output file format based on selection
                // not sure why I need to specify the button isn't nil, since one is ALWAYS selected, but I was getting a fatal error without doing so
        if (Button1 != nil) && Button1.state == .on {
            outputExtension = ".extA"
        } else if (Button2 != nil) && Button2.state == .on {
            outputExtension = ".extB"
        } else if (Button3 != nil) && Button3.state == .on {
            outputExtension = ".extC"
        } else if (Button4 != nil) && Button4.state == .on {
            outputExtension = ".extD"
        } else {
            outputExtension = ".extE"
        }
    }
}

Я уверен, что упускаю что-то очевидное, но, как я уже сказал, я впервые работаю с несколькими контроллерами представления, и я не уверен, что реализовал их должным образом, и я кодировал только несколько недель,так что я не могу определить, где я иду не так.

1 Ответ

0 голосов
/ 19 октября 2019

Я предполагаю, что outputTextDisplay - это IBOutlet в InOutViewController, и он объявлен как неявно развернутый. (Примерно так:)

var outputTextDisplay: UITextField!

Если вы ссылаетесь на такую ​​переменную из одного из IBActions в вашем InOutViewController, все хорошо, потому что в этот момент загружаются представления вашего контроллера представления.

Если, с другой стороны, вы вызываете updateOutputText() с другого контроллера представления, ваши InOutViewController представления, возможно, еще не были загружены, поэтому выход для outputTextDisplay по-прежнему равен нулю. Когда переменная объявляется как неявно развернутая (с использованием ! в конце типа), то каждый раз, когда вы ссылаетесь на нее, компилятор принудительно разворачивает ее, и если она равна nil, вы вылетаете.

Выследует изменить updateOutputText() на ?, чтобы развернуть переменную. Это останавливает неявно развернутые опции от сбоя. Примерно так:

func updateOutputText() {
    // derive output file path and name from input if no output location is chosen 
    if inputFileUrl != nil && outputDirectoryUrl == nil {
        let outputDirectory = inputFileUrl!.deletingPathExtension()
        let outputDirectoryPath = outputDirectory.path
        let outputPath = outputDirectoryPath + "(outputExtension)"
        outputTextDisplay?.stringValue = outputPath //Note the `?`
    } else if inputFileUrl != nil && outputDirectoryUrl != nil {
     // derive default file name from input but use selected output path if one is chosen
        let outputDirectoryPath = outputDirectoryUrl!.path
        let outputFile = inputFileUrl!.deletingPathExtension()
        let outputFilename = outputFile.lastPathComponent
        // derive file extension from getOutputOption method of OptionsViewController class
        let outputPath = outputDirectoryPath + "/" + outputFilename + "(outputExtension)"
        outputTextDisplay?.stringValue = outputPath  //Note the `?`
       }
    }
}

В коде outputTextDisplay?.stringValue = outputPath используется «необязательное сцепление», которое заставляет компилятор проверить, равен ли outputTextDisplay ноль, и прекратить выполнение кода, если оно есть.

Обратите внимание, что если вы сделаете это изменение, ваше строковое значение не будет установлено в поле outputTextDisplay при вызове функции updateOutputText(). Вам нужно будет установить значение после вызова viewDidLoad(). (Возможно, в вашем viewDidLoad или viewWillAppear.)

Редактировать:

Этот код:

@IBAction func radioButtonClicked(_ sender: Any) {
    getOutputOption()
        // update display with new file extension
    InOutViewController().updateOutputText()
}

Это очень неправильно. В ответ на нажатие переключателем пользователя бит InOutViewController() создает одноразовый экземпляр InOutViewController, пытается вызвать его метод updateOutputText(), а затем забывает о вновь созданном InOutViewController. Не делайте этого.

Вам нужен способ отслеживать контроллеры дочерних представлений, которые находятся на экране. Чтобы показать вам, как это сделать, вам нужно объяснить, как создаются ваши различные контроллеры представления. Вы используете встроенные сегы?

...