0

我在创建一个 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 = ""

OptionsViewController班级的@IBOutlets

@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_ 当我从其中的操作方法中调用该方法时,它工作正常。@IBActionOptionsViewControllerInOutViewController

以下是类中的@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"
        }
    }
}

我确定我遗漏了一些明显的东西,但就像我说的,这是我第一次使用多个视图控制器,我不确定我是否正确地实现了它们,而且我只编码了几个星期,所以我无法发现我哪里出错了。

4

1 回答 1

0

我的猜测是这outputTextDisplay是一个 IBOutlet InOutViewController,它被声明为隐式展开。(像这样的东西:)

var outputTextDisplay: UITextField!

如果您从 中的 IBAction 之一引用这样的变量InOutViewController,那么一切都很好,因为此时您的视图控制器的视图已加载。

另一方面,如果您updateOutputText()从另一个视图控制器调用,您InOutViewController的视图可能尚未加载,因此 outputTextDisplay 的出口仍然为零。当一个变量被声明为隐式解包(!在类型末尾使用 a )时,任何时候你引用它,编译器都会强制解包它,如果它为 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,如果是则停止执行代码。

outputTextDisplay请注意,如果您进行更改,则在调用该updateOutputText()函数时,您的字符串值不会安装到您的字段中。您需要在viewDidLoad()调用后安装该值。(可能在您的 viewDidLoad 或 viewWillAppear 中。)

编辑:

这段代码:

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

是非常错误的。作为对用户单击单选按钮的响应,该InOutViewController()位创建一个可丢弃的 an 实例InOutViewController,尝试调用其updateOutputText()方法,然后忘记新创建的InOutViewController. 不要那样做。

您需要一种方法来跟踪屏幕上的子视图控制器。为了向您展示如何做到这一点,您需要解释如何创建各种视图控制器。你在使用嵌入segues吗?

于 2019-10-18T23:37:21.813 回答