Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
167 views
in Technique[技术] by (71.8m points)

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

I am having an issue with my two child view controllers inside a parent PageViewController, where a delegate called by one of the children is not received by the other child.

My first child contains buttons, and when a button is pressed, a delegate is triggered in the other child to pause the timer. However, it fails to receive the call and the timer continues to run.

Here is my 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
    }
}

My ChildViewController with Buttons:

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()
    }

}

My other ChildViewController, which should be receiving the calls of the 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)
    }

}

Sorry that this is so long winded, been trying for quite a while and really annoyed that it doesn't work! Thanks!

question from:https://stackoverflow.com/questions/65713855/child-view-controllers-in-page-view-controller-failing-to-receive-delegate-calls

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I'll try to be clear, hopefully i'll be so as english is not my native language.

It seems to me that you are instantiating your ViewControllers to be presented in the getNewViewController() method and storing them in the workoutViewControllers array, but you are setting the delegate as a separate instance that you never set in your PageVC. You need to set the delegates using the same instances.

These two are two instances of two VC classes (also not sure if the identifier "DisplayViewController" is right, i expected "DisplayMaplessViewController", hard to tell without the storyboard):

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

And these in the array two other instances, unrelated from the ones above, of the same two classes:

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

To better understand what i mean, i refactored from scratch and semplified your project (had to do it programmatically as i'm not used to storyboards). It now consists of a PageController that displays a buttonsVC with a red button and a displayMaplessVC with a blue background. Once you press the red button, the delegate method is called which causes the blue background to turn green.
Take a look at what i'm doing, as i'm appending the same instances of which i set the delegate:

  1. instantiate a DisplayMaplessViewController object and ButtonsViewController object;
  2. set buttonsVC.buttonsDelegate = displayMaplessVC;
  3. append both ViewControllers to the array.

This is a way to get it done but for sure there are several other ways to achieve the same result, once you get the point and understand your mistake you can pick the one you like the most.

Just copy and paste it into a new project, build and run (you have to set the class of the starting ViewController in the Storyboard as 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
}

}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...