XCODE SWIFT 如何在按下按钮后执行代码,但在另一个 file/class 中?

XCODE SWIFT How do I execute code after a button is pressed, but in another file/class?

我正在使用MapKit,用户可以添加注释。他们可以点击屏幕,这会提示他们是否要使用 UIAlert 添加注释,如果他们说是,它会显示另一个视图控制器,以便用户可以输入有关注释的信息,如位置名称、描述等. 该视图控制器在顶部有一个 'Done' BarButtonItem 来确认他们输入的信息,并创建注释。

@IBAction func doneButtonPressed(_ sender: UIBarButtonItem) {
        doneButtonHasBeenPressed = true
        dismiss(animated: true, completion: nil)
}

问题是,必须在原始视图控制器中的 'touchesEnded' 函数中创建注释,将用户发送到他们输入注释信息的视图控制器,因为这是它获取的地方来自(使用 touchesEnded)的 CLCoordinate2D。正是在同一个 touchesEnded 函数中,我将用户发送到下一个视图控制器。这是 touchesEnded 代码及其使用的辅助函数:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    
    if let touch = touches.first {
        let touchLocation = touch.location(in: view)
        
        // Converts CGPoint coordinates and UIView to CLLocationCordinate2D (map coordinates) Remember to rename if addButtonPressed order of creation of annotation gets changed!
        let coordinatesTouchedToCreateAnnotation = mapView.convert(touchLocation, toCoordinateFrom: view)
        
        if userIsAllowedToAddAnnotation {
            
            let alertController = UIAlertController(title: "Confirm", message: "Are you sure you want to add a jump location here?", preferredStyle: .alert)
            
            let noAction = UIAlertAction(title: "No", style: .cancel, handler: nil)
            
            let yesAction = UIAlertAction(title: "Yes", style: .default) { (action) in
                
                // Segue takes user to JumpSpotCreatorController to input information about the jump location
                self.performSegue(withIdentifier: "CreateJumpSpot", sender: self)
                
                
                    if self.jumpSpotCreatorController.doneButtonHasBeenPressed == true {

                        self.jumpSpotCreatorController.doneButtonHasBeenPressed = false
                        self.createJumpSpotAnnotation(coordinatesDeterminedByTouch: coordinatesTouchedToCreateAnnotation)
                        self.userIsAllowedToAddAnnotation = false
                        self.tapToAddJumpSpotLabel.isHidden = true

                    }
                
                
                
            }
            alertController.addAction(noAction)
            alertController.addAction(yesAction)
            
            present(alertController, animated: true, completion: nil)
            
        } else {
            return
        }
    }
}

// Force unwrap is okay because this will only be called if 'Done' button is pressed in the JumpSpotCreatorController, which mandates that those inputs not be nil.
func createJumpSpotAnnotation(coordinatesDeterminedByTouch: CLLocationCoordinate2D) {
    mapView.addAnnotation(JumpSpotAnnotation(name: jumpSpotCreatorController.nameTextField.text!, coordinate: coordinatesDeterminedByTouch, estimatedHeight: jumpSpotCreatorController.estimatedHeightTextField.text!, locationDescription: jumpSpotCreatorController.descripitionTextView.text, warnings: jumpSpotCreatorController.warningsTextView.text ?? "", image: jumpSpotCreatorController.jumpSpotImageView.image ?? UIImage(imageLiteralResourceName: "Image-1")))
}

如您所见,在 touchesEnded 函数中创建注释的代码块(位于我向 alertController 添加操作的正上方,以防您找不到它。它大约有 4 行)是立即执行,而不是在我需要时执行,即在我的其他视图控制器 (JumpSpotCreatorController) 中按下 'Done' 按钮后。我尝试使用 doneButtonHasBeenPressed 变量修复它,但它没有任何区别(原因很明显)。我怎样才能在按下完成按钮后才执行它?我无法将另一个视图控制器初始化为主视图控制器中的对象(带有 touchesEnded 的是主视图控制器),因为它会在两个视图控制器之间创建一个无限循环的引用。 DispatchQueue 能以某种方式提供帮助吗?我已经研究了几个小时,但不太清楚如何在这里应用它。非常感谢。

我没有 100% 关注,但这有帮助吗:

  • 按钮被点击并且你在“A”

  • “A”调用 B 做某事,AND,

  • “A”传递对自身的引用

(也就是说,为B中的那个调用“添加一个参数”,它是class“A”的一个变量。假设这个变量被命名为“callMeWhenYou'reFinished”)

  • 当“B”完成它必须做的事情时

  • 只需调用 callMeWhenYou'reFinished#WhateverFunction

...你走吧!

作为一种“一般编程问题”(忘记 Swift 中可用的内容等),您只是在描述“回调”。如果我没听错,希望对你有所帮助!


进一步:

有一些循环引用是完全可以的。 (所以,A 知道 B 并且 B 得到了对 A 中某事的回调。)

显然,如果您随后错误地进行了无限循环,那就是无限循环。但是“不小心造成无限循环”与您是否有单独的 classes、引用等完全没有关系

如果我没猜错,你正在尝试得到这样的东西:

Class A {
   weak var referenceToB: B?
   @IBAction func buttonAction(_ sender: Any) {
      guard var referenceToB = referenceToB else {
          fatalError("referenceToB not set!")
      }
      referenceToB!.otherFunction()
   }
}
Class B {
   func otherFunction() {
      //stuff
   }
}

在你的例子中,在你实例化你的 VC 之后,分配一个对包含所需功能的 class 对象的引用。

有多种方法可以做到这一点 --- 各有利弊。

此方法使用委托/协议模式。

我们定义了一个协议,它将允许 类 在他们的委托中执行函数 类:

// protocol / delegate pttern
protocol JumpSpotDelegate: class {
    func createJumpSpotAnnotation(_ name: String, estimatedHeight: String, locationDescription: String, warnings: String, image: UIImage?)
    func cancelAnnotation()
}

在您的地图视图的控制器中,我们符合该委托:

class MapViewController: UIViewController, JumpSpotDelegate {

JumpSpotCreatorController中,我们设置了一个delegate属性:

class JumpSpotCreatorController: UIViewController {
    weak var delegate: JumpSpotDelegate?

当我们导航到 JumpSpotCreatorController 时,我们将自己指定为其委托:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // make sure we're acting on the correct segue
        if segue.identifier == "CreateJumpSpot", let vc = segue.destination as? JumpSpotCreatorController {
            // set the delegate in the JumpSpotCreatorController we're navigating to
            vc.delegate = self
        }
    }

JumpSpotCreatorController中,当点击完成按钮时,我们通过委托函数告诉地图控制器:

    delegate?.createJumpSpotAnnotation(name, estimatedHeight: estimatedHeight, locationDescription: description, warnings: warnings, image: img)

一起来了。我通过 let 语句添加了对象来编写此...我希望您将它们作为 @IBOutlet 连接:

// protocol / delegate pttern
protocol JumpSpotDelegate: class {
    func createJumpSpotAnnotation(_ name: String, estimatedHeight: String, locationDescription: String, warnings: String, image: UIImage?)
    func cancelAnnotation()
}

class MapViewController: UIViewController, JumpSpotDelegate {
    
    // this will hold the touch point while we navigate to and back from the JumpSpotCreatorController
    var lastTouch: CLLocationCoordinate2D?
    
    let mapView: MKMapView!
    
    var userIsAllowedToAddAnnotation = true
    let tapToAddJumpSpotLabel = UILabel()
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        if let touch = touches.first {
            let touchLocation = touch.location(in: view)
            
            // Converts CGPoint coordinates and UIView to CLLocationCordinate2D (map coordinates)
            // store in class property
            self.lastTouch = mapView.convert(touchLocation, toCoordinateFrom: view)
            
            if userIsAllowedToAddAnnotation {
                
                let alertController = UIAlertController(title: "Confirm", message: "Are you sure you want to add a jump location here?", preferredStyle: .alert)
                
                let noAction = UIAlertAction(title: "No", style: .cancel, handler: nil)
                
                let yesAction = UIAlertAction(title: "Yes", style: .default) { (action) in
                    
                    // Segue takes user to JumpSpotCreatorController to input information about the jump location
                    self.performSegue(withIdentifier: "CreateJumpSpot", sender: self)
                    
                }
                alertController.addAction(noAction)
                alertController.addAction(yesAction)
                
                present(alertController, animated: true, completion: nil)
                
            } else {
                return
            }
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // make sure we're acting on the correct segue
        if segue.identifier == "CreateJumpSpot", let vc = segue.destination as? JumpSpotCreatorController {
            // set the delegate in the JumpSpotCreatorController we're navigating to
            vc.delegate = self
        }
    }

    // called by Button action in JumpSpotCreatorController
    func createJumpSpotAnnotation(_ name: String, estimatedHeight: String, locationDescription: String, warnings: String, image: UIImage?) {
        // the coordinate parameter was stored in our class property "lastTouch"
        guard let lastTouch = self.lastTouch else {
            // self.lastTouch was not set!
            return
        }
        mapView.addAnnotation(JumpSpotAnnotation(name: name, coordinate: lastTouch, estimatedHeight: estimatedHeight, locationDescription: description, warnings: warnings, image: image ?? UIImage(imageLiteralResourceName: "Image-1")))
        
        self.userIsAllowedToAddAnnotation = false
        self.tapToAddJumpSpotLabel.isHidden = true

        // pop from JumpSpotCreatorController back to self
        self.navigationController?.popViewController(animated: true)
    }
    
    // I'm assuming you would also have a Cancel button?
    func cancelAnnotation() {
        self.lastTouch = nil
        // pop from JumpSpotCreatorController back to self
        self.navigationController?.popViewController(animated: true)
    }

}

class JumpSpotCreatorController: UIViewController {
    weak var delegate: JumpSpotDelegate?

    let nameTextField = UITextField()
    let estimatedHeightTextField = UITextField()
    let descripitionTextView = UITextView()
    let warningsTextView = UITextView()
    let jumpSpotImageView = UIImageView()
    
    @IBAction func doneBtnTapped() {
        // presumably, you'll validate all these
        guard let name = nameTextField.text,
            let estimatedHeight = estimatedHeightTextField.text,
            let description = descripitionTextView.text,
            let warnings = warningsTextView.text,
            let img = jumpSpotImageView.image else {
                // notify user required fields were blank
                return
        }
        
        delegate?.createJumpSpotAnnotation(name, estimatedHeight: estimatedHeight, locationDescription: description, warnings: warnings, image: img)
    }
    
    @IBAction func cancelBtnTapped() {
        delegate?.cancelAnnotation()
    }
}