Child 页面视图控制器中的视图控制器无法接收委托调用

Child View Controllers in Page View Controller Failing to Receive Delegate Calls

我在 parent PageViewController 中的两个 child 视图控制器出现问题,其中 children 之一调用的委托未被另一个 child.

我的第一个 child 包含按钮,当按下一个按钮时,另一个 child 会触发一个委托来暂停计时器。但是,它没有收到呼叫,计时器继续 运行。

这是我的 PageViewController:

class StartMaplessWorkoutPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {

    lazy var workoutViewControllers: [UIViewController] = {
        return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.dataSource = self
        
        // Saw this from another answer, doesn't do anything that helps (at the moment)
        let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
        let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayMaplessViewController") as! DisplayMaplessViewController
        
        buttonsViewController.buttonsDelegate = displayMaplessViewController
        
        if let firstViewController = workoutViewControllers.last {
            setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
        }
        
        let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [StartWorkoutPageViewController.self])
        pageControl.currentPageIndicatorTintColor = .orange
        pageControl.pageIndicatorTintColor = .gray
    }
    
    func getNewViewController(viewController: String) -> UIViewController {
        return (storyboard?.instantiateViewController(withIdentifier: viewController))!
    }
    
    // MARK: PageView DataSource
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
            return nil
        }
        
        let previousIndex = viewControllerIndex - 1
        
        guard previousIndex >= 0 else {
            return workoutViewControllers.last
        }
        
        guard workoutViewControllers.count > previousIndex else {
            return nil
        }
        
        return workoutViewControllers[previousIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
            return nil
        }
        
        let nextIndex = viewControllerIndex + 1
        let workoutViewControllersCount = workoutViewControllers.count
        
        guard workoutViewControllersCount != nextIndex else {
            return workoutViewControllers.first
        }
        
        guard workoutViewControllersCount > nextIndex else {
            return nil
        }
        
        return workoutViewControllers[nextIndex]
    }
    
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return workoutViewControllers.count
    }
    
    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = workoutViewControllers.firstIndex(of: firstViewController) else {
            return 0
        }
        
        return firstViewControllerIndex
    }
}

我的带按钮的 ChildViewController:

protocol ButtonsViewDelegate: class {
    func onButtonPressed(button: String)
}

class ButtonsViewController: UIViewController {
    
    weak var buttonsDelegate: ButtonsViewDelegate?
    
    var isPaused: Bool = false
    
    @IBOutlet weak var startStopButton: UIButton!
    @IBOutlet weak var optionsButton: UIButton!
    @IBOutlet weak var endButton: UIButton!
    
    @IBAction func startStopButton(_ sender: Any) {
        if isPaused == true {
            buttonsDelegate?.onButtonPressed(button: "Start")
            isPaused = false
        } else {
            buttonsDelegate?.onButtonPressed(button: "Pause")
            isPaused = true
        }
    }
    
    @IBAction func endButton(_ sender: Any) {
        let menu = UIAlertController(title: "End", message: "Are you sure you want to end?", preferredStyle: .actionSheet)
        
        let end = UIAlertAction(title: "End", style: .default, handler: { handler in
            self.buttonsDelegate?.onButtonPressed(button: "End")
        })
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
        menu.addAction(end)
        menu.addAction(cancelAction)
        
        self.present(menu, animated: true, completion: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

我的另一个 ChildViewController,它应该接收 ButtonsViewDelegate 的调用:

import UIKit

class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {
    
    var timer = Timer()
    var currentTime: TimeInterval = 0.0
    var isCountdown: Bool = false
    var isInterval: Bool = false
    var currentRepeats: Int = 0
    var currentActivity: Int = 0
    var count: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        startIntervalTimer(withTime: 0)
    }

    // Currently not being called
    func onButtonPressed(button: String) {
        switch button {
        case "Start":
            restartIntervalTimer()
        case "Pause":
            pauseIntervalTimer()
        case "End":
            stop()
        default:
            break
        }
    }
    
    func startIntervalTimer(withTime: Double) {
        if withTime != 0 {
            currentTime = withTime
            if isInterval != true {
                isCountdown = true
            }
        }

        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
    }
    
    func pauseIntervalTimer() {
        timer.invalidate()
    }
    
    func restartIntervalTimer() {
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
    }

    // Currently Not being called
    func stop() {
        timer.invalidate()
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .positional
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.zeroFormattingBehavior = [.pad]
        
        let timeString = formatter.string(from: currentTime)
        
        // save the data etc

        print("Stop is called")
    }
    
    @objc func intervalTimerUpdate() {
        currentTime += 1.0
        print(currentTime)
    }

}

抱歉,这太冗长了,已经尝试了很长时间,但真的很生气,因为它不起作用!谢谢!

我会尽量说清楚,希望我能说清楚,因为英语不是我的母语。

在我看来,您正在实例化要在 getNewViewController() 方法中显示的 ViewControllers 并将它们存储在 workoutViewControllers 数组中,但您将委托设置为您从未在 PageVC 中设置的单独实例。您需要使用相同的实例设置委托。

这两个是两个实例 of 两个 VC classes(也不确定标识符“DisplayViewController”是否正确,我预计是“DisplayMaplessViewController”,如果没有情节提要很难判断):

    let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
    let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayViewController") as! DisplayMaplessViewController
    
    buttonsViewController.buttonsDelegate = displayMaplessViewController

数组中的这些 两个其他实例,与上面的实例无关,相同的两个 classes :

    lazy var workoutViewControllers: [UIViewController] = {
    return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
}()

为了更好地理解我的意思,我从头开始重构并简化了您的项目(必须以编程方式进行,因为我不习惯故事板)。 它现在由一个 PageController 组成,它显示一个带有红色按钮的 buttonsVC 和一个带有蓝色背景的 displayMaplessVC。 按下红色按钮后,将调用委托方法,使蓝色背景变为绿色。
看看我在做什么,因为我附加了我设置委托的相同实例:

  1. 实例化一个 DisplayMaplessViewController 对象和 ButtonsViewController 对象;
  2. 设置buttonsVC.buttonsDelegate = displayMaplessVC;
  3. 将两个 ViewController 附加到数组。

这是一种完成它的方法,但可以肯定的是,还有其他几种方法可以达到相同的结果,一旦您明白了要点并了解了自己的错误,就可以选择最喜欢的一种。

只需将其复制并粘贴到一个新项目中,构建并 运行(您必须将 Storyboard 中起始 ViewController 的 class 设置为 StartMaplessWorkoutPageViewController ):

import UIKit

class StartMaplessWorkoutPageViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {

private var workoutViewControllers = [UIViewController]()
private let pageController: UIPageViewController = {
    let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    return pageController
}()

override func viewDidLoad() {
    super.viewDidLoad()
    pageController.delegate = self
    pageController.dataSource = self
    let buttonsVC = ButtonsViewController()
    let displayMaplessVC = DisplayMaplessViewController()
    buttonsVC.buttonsDelegate = displayMaplessVC
    workoutViewControllers.append(buttonsVC)
    workoutViewControllers.append(displayMaplessVC)
    
    self.addChild(self.pageController)
    self.view.addSubview(self.pageController.view)

    self.pageController.setViewControllers([displayMaplessVC], direction: .forward, animated: true, completion: nil)

    self.pageController.didMove(toParent: self)
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    pageController.view.frame = view.bounds
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    
    guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
         return nil
     }
     
     let previousIndex = viewControllerIndex - 1
     
     guard previousIndex >= 0 else {
         return workoutViewControllers.last
     }
     
     guard workoutViewControllers.count > previousIndex else {
         return nil
     }
     
     return workoutViewControllers[previousIndex]
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    
    guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
        return nil
    }
    
    let nextIndex = viewControllerIndex + 1
    let workoutViewControllersCount = workoutViewControllers.count
    
    guard workoutViewControllersCount != nextIndex else {
        return workoutViewControllers.first
    }
    
    guard workoutViewControllersCount > nextIndex else {
        return nil
    }
    
    return workoutViewControllers[nextIndex]
}

func presentationCount(for pageViewController: UIPageViewController) -> Int {
    return workoutViewControllers.count
}

}

.

protocol ButtonsViewDelegate: class {
    func onButtonPressed()
}

import UIKit

class ButtonsViewController: UIViewController {

weak var buttonsDelegate: ButtonsViewDelegate?

let button: UIButton = {
    let button = UIButton()
    button.backgroundColor = .red
    button.addTarget(self, action: #selector(onButtonPressed), for: .touchUpInside)
    return button
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(button)
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    button.frame = CGRect(x: 50,
                          y: 50,
                          width: 100,
                          height: 100)
}

@objc private func onButtonPressed() {
    buttonsDelegate?.onButtonPressed()
}

}

.

import UIKit

class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {

private let testView: UIView = {
    let view = UIView()
    view.backgroundColor = .blue
    return view
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(testView)
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    testView.frame = view.bounds
}

internal func onButtonPressed() {
    testView.backgroundColor = .green
}

}