Xcode 在从另一个 ViewController class 调用方法时,找到一个 nil 变量,其中 none 应该是
Xcode finding a nil variable where none should be when methods is called from another ViewController class
我在创建一个 MacOS 应用程序(我的第一个应用程序,它不是 "Your First App" 教程的一部分)的过程中途,在主要 window 中涉及很多用户选项,我受够了事实上,我的 ViewController 文件已经变得笨拙,在很长一段时间内都无法维护 运行。
我决定打破它以较小的块输入多个视图控制器,以便在 UIBuilder 中使用容器视图嵌入视图使其更易于管理,但我发现的所有教程都是针对 Xcode/Swift 的过时版本,或者关于在 iOS 中管理多个视图,所以我不得不进行一些推断,我可能做错了。
现在,当一个 ViewController 中的方法被另一个 ViewController 调用时,即使该方法在其自己的视图控制器调用时可以正常工作,我也会收到错误消息。
要么我遗漏了一些明显的东西,要么我设置错误。
全局变量:
var inputPathUrl = URL?
var outputExtension: String = ""
@IBOutlets 和 InOutViewController
class:
的本地属性
@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
class
@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
class:
@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
为 [=20 中的输出格式单选按钮调用此方法时=] 在选择不同的文件扩展名时更新输出显示。当我从 InOutViewController
中的操作方法调用该方法时,它工作正常。
这里是 @IBAction
方法和 getOutputOption
方法来自 OptionsViewController
class:
@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"
}
}
}
我确定我遗漏了一些明显的东西,但就像我说的,这是我第一次使用多个视图控制器,我不确定我是否正确地实现了它们,我只是在为几个星期,所以我无法发现我哪里出错了。
我的猜测是 outputTextDisplay
是 InOutViewController
中的一个 IBOutlet,它被声明为隐式解包。 (像这样:)
var outputTextDisplay: UITextField!
如果您从 InOutViewController
中的一个 IBAction 中引用这样的变量,一切都很好,因为此时您的视图控制器的视图已加载。
另一方面,如果您从另一个视图控制器调用 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
是否为 nil,如果是则停止执行代码。
请注意,如果您进行了更改,您的字符串值将不会在您调用 updateOutputText()
函数时安装到您的 outputTextDisplay
字段中。您需要在调用 viewDidLoad()
之后安装该值。 (可能在您的 viewDidLoad 或 viewWillAppear 中。)
编辑:
此代码:
@IBAction func radioButtonClicked(_ sender: Any) {
getOutputOption()
// update display with new file extension
InOutViewController().updateOutputText()
}
大错特错。响应用户单击单选按钮,InOutViewController()
位创建一个 InOutViewController
的一次性实例,尝试调用它的 updateOutputText()
方法,然后忘记新创建的 InOutViewController
。不要那样做。
您需要一种方法来跟踪屏幕上的子视图控制器。为了向您展示如何做到这一点,您需要解释如何创建各种视图控制器。您在使用嵌入转场吗?
我在创建一个 MacOS 应用程序(我的第一个应用程序,它不是 "Your First App" 教程的一部分)的过程中途,在主要 window 中涉及很多用户选项,我受够了事实上,我的 ViewController 文件已经变得笨拙,在很长一段时间内都无法维护 运行。
我决定打破它以较小的块输入多个视图控制器,以便在 UIBuilder 中使用容器视图嵌入视图使其更易于管理,但我发现的所有教程都是针对 Xcode/Swift 的过时版本,或者关于在 iOS 中管理多个视图,所以我不得不进行一些推断,我可能做错了。
现在,当一个 ViewController 中的方法被另一个 ViewController 调用时,即使该方法在其自己的视图控制器调用时可以正常工作,我也会收到错误消息。
要么我遗漏了一些明显的东西,要么我设置错误。
全局变量:
var inputPathUrl = URL?
var outputExtension: String = ""
@IBOutlets 和 InOutViewController
class:
@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
class
@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
class:
@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
为 [=20 中的输出格式单选按钮调用此方法时=] 在选择不同的文件扩展名时更新输出显示。当我从 InOutViewController
中的操作方法调用该方法时,它工作正常。
这里是 @IBAction
方法和 getOutputOption
方法来自 OptionsViewController
class:
@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"
}
}
}
我确定我遗漏了一些明显的东西,但就像我说的,这是我第一次使用多个视图控制器,我不确定我是否正确地实现了它们,我只是在为几个星期,所以我无法发现我哪里出错了。
我的猜测是 outputTextDisplay
是 InOutViewController
中的一个 IBOutlet,它被声明为隐式解包。 (像这样:)
var outputTextDisplay: UITextField!
如果您从 InOutViewController
中的一个 IBAction 中引用这样的变量,一切都很好,因为此时您的视图控制器的视图已加载。
另一方面,如果您从另一个视图控制器调用 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
是否为 nil,如果是则停止执行代码。
请注意,如果您进行了更改,您的字符串值将不会在您调用 updateOutputText()
函数时安装到您的 outputTextDisplay
字段中。您需要在调用 viewDidLoad()
之后安装该值。 (可能在您的 viewDidLoad 或 viewWillAppear 中。)
编辑:
此代码:
@IBAction func radioButtonClicked(_ sender: Any) {
getOutputOption()
// update display with new file extension
InOutViewController().updateOutputText()
}
大错特错。响应用户单击单选按钮,InOutViewController()
位创建一个 InOutViewController
的一次性实例,尝试调用它的 updateOutputText()
方法,然后忘记新创建的 InOutViewController
。不要那样做。
您需要一种方法来跟踪屏幕上的子视图控制器。为了向您展示如何做到这一点,您需要解释如何创建各种视图控制器。您在使用嵌入转场吗?